Note: This document describes the process I used to set up poste.io in an LX zone on SmartOS. It may also be used as a guide to the steps needed for converting other docker images to run as LX zones with appropriate customization.
imgadm import analogic/poste.io
Note the uuid of the image (it will be on the first line)
Create a vmadm json file:
{ "brand": "lx", "kernel_version": "3.13.0", "autoboot": false, "delegate_dataset": true,
"docker": "true", "image_uuid": "2a9c872d-b36d-2c84-aa80-37250b541164",
"alias": "smtp", "hostname": "smtp", "max_physical_memory": 2048, "quota": 20, "cpu_cap": 75, "resolvers": ["1.1.1.1", "1.0.0.1"] , "nics": [ { "nic_tag": "admin", "ips": ["YOURIP/PREFIX"], "gateways": ["YOURGW"] } ],
"internal_metadata": { "docker:cmd": "[\"/init\"]", "docker:env": "[\"HTTPS=ON\", \"HTTPS_PORT=443\", \"HTTP_PORT=80\", \"APP_ENV=prod\", \"MODE=free\"]" } }
Create the zone:
vmadm create -f mailserver.json
Note that there are some keys in the internal_metadata object that are used by dockerinit and correspond to the environment variable settings and run command specified in an image's Dockerfile and overridden on the docker command-line.
Unlike docker containers, data storage in LX zones is persistent. That means that it's not strictly necessary to mount data storage locations using the docker "volume" commands. However, in order to make upgrades easier, we created the zone with a delegated dataset. When we use the vmadm reprovision command, the main filesystem is replaced with the new image, but any data stored in the delegated dataset is kept. So, we can symlink or lofi-mount all of the data storage locations in the runnng system to locations in the delegated dataset as needed. Note that this whole plan is slightly complicated by the fact that dockerinit doesn't handle mounting the delegated dataset. So, we will create a script that does both of these things and drop it into the appropriate location so that this all happens early in the startup process.
The poste.io docker container uses s6 as its init system, so the following script is compatible with the way that s6 works. If you're using sysvinit or something else, you will need to modify it accordingly. You'll want to have this run as early as possible so that the storage locations are set up before anything that depends on storing data in one of those locations actually starts.
Copy the following file to /zone/ZONEID/root/etc/cont-init.d/01-init-zfs.sh:
#!/usr/bin/env bash ##!/usr/bin/with-contenv bash DELEGATED_DATASET=`/native/sbin/zfs list | cut -f 1 -d ' ' | grep "^zones/[0-9a-f-]*/data\$"` DATASET_MOUNTPOINT=/data if [ -n $DELEGATED_DATASET ] ; then echo setting mountpoint of dataset to $DATASET_MOUNTPOINT /native/sbin/zfs set mountpoint=$DATASET_MOUNTPOINT $DELEGATED_DATASET echo mounting zfs datasets. /native/sbin/zfs mount -va echo "mounting /data/log to /var/log via lofs" if [ ! -d $DATASET_MOUNTPOINT/log/pre-lofs ] ; then mkdir -p $DATASET_MOUNTPOINT/log/pre-lofs fi cp -rf /var/log/* /data/log/pre-lofs /native/sbin/mount -F lofs -orw $DATASET_MOUNTPOINT/log /var/log fi
Also, comment out the first set of lines that deal with /var/log and /data/log in 02-directories.sh
Smtp server configuration
Edit the following 2 files:
/opt/haraka-smtp/config/smtp.ini
Change listen to the following:
listen=0.0.0.0:25,[::0]:25
/opt/haraka-submission/config/smtp.ini
Change listen to the following:
listen=0.0.0.0:587,0.0.0.0:465,[::0]:587,[::0]:465
p0f
p0f is used to fingerprint connections from the SMTP server. Unfortunately the debian version of p0f uses ETHTOOL ioctls to figure out things like link speed. This isn't supported and that causes the SMTP server to hang up on connections. Fortunately, we can use a native p0f and insert it into the zone. The magic of LX is that it knows how to run not only Linux binaries, but also native illumos binaries as well. There's a whole tree of native stuff inside /native that can be used (and indeed must be used if you want to do things like fiddle with the ipf firewall). pkgsrc has p0f but it's an older version that doesn't work the way poste.io expects. No problem-o...
We just get the latest version and compile it in a native zone:
git clone https://github.com/p0f/p0f
pkgin -y in build-essential libpcap
there's a script in there called build.sh, but it needs some tweaks... $OSTYPE is solaris2.11, so we need to change the spot where it looks for plain "solaris" and fix that. Also, we need to add "-m64" to the base CFLAGS definition. After that, run build.sh and you should have a working native p0f.
in your poste.io zone, create a directory to hold p0f and the native pcap libs... it can be anywhere, but inside /data makes sense:
mkdir -p /data/native_p0f
copy your native p0f and /opt/local/lib/libpcap* there as well. You'll get some extra files like static libs but I didn't bother to skip over them.
mv /usr/sbin/p0f out of the way and in its place, add the following:
#!/bin/sh LD_LIBRARY_PATH=/data/native_p0f export LD_LIBRARY_PATH exec /data/native_p0f/p0f -f /etc/p0f/p0f.fp "$@"
make sure to set to be executable and things should "magically" start working.
ipf
We could simply use SmartOS' firewall functionality via vmadm, but I already had a standard ruleset, so here's how you employ that (again, using s6's script pattern)
copy /etc/protocols to zone (otherwise ipf may behave strangely.)
create /etc/cont-init.d/06-ipf.sh:
#!/usr/bin/with-contenv bash echo "Setting up IPF" if [[ -f /etc/ipf/ipf.conf ]]; then echo "Enabling IPF rules for IPv4" /native/usr/sbin/ipf -E -Fa -f /etc/ipf/ipf.conf fi if [[ -f /etc/ipf/ipf6.conf ]]; then echo "Enabling IPF rules for IPv6" /native/usr/sbin/ipf -E -6 -f /etc/ipf/ipf6.conf fi
/etc/ipf/ipf.conf:
block in all with frag pass out quick on eth0 proto tcp from eth0/32 to any keep state pass out quick on eth0 proto udp from eth0/32 to any keep state pass out quick on eth0 proto icmp from eth0/32 to any keep state block in on eth0 all block in quick on eth0 from 192.168.0.0/16 #RFC 1918 private IPto any block in quick on eth0 from 172.16.0.0/12 #RFC 1918 private IPto any block in quick on eth0 from 10.0.0.0 #RFC 1918 private IP0/ 8 to any block in quick on eth0 from 127.0.0.0/8 #loopback8 to any block in quick on eth0 from 0.0.0.0/8 #loopback0/ 8 to any block in quick on eth0 from 169.254.0.0/16 #DHCP auto-configto any block in quick on eth0 from 192.0.2.0/24 #reserved for docs24 to any block in quick on eth0 from 204.152.64.0/23 to any #Sun cluster interconnect block in quick on eth0 from 224.0.0.0/3 to any #Class D & E multicast # Block fragments and too short tcp packets block in quick on eth0 all with frags block in quick on eth0 proto tcp all with short # block source routed packets block in quick on eth0 all with opt lsrr block in quick on eth0 all with opt ssrr # Block OS fingerprint attempts and log first occurrence block in log first quick on eth0 proto tcp from any to any flags FUP # Block anything with special options block in quick on eth0 all with ipopts # Block public pings and ident #block in quick on eth0 proto icmp all icmp-type 8 block in quick on eth0 proto tcp from any to any port = 113 pass in quick on eth0 proto icmp from any to any pass in quick on eth0 proto tcp from any to any port = 80 pass in quick on eth0 proto tcp from any to any port = 443 pass in quick on eth0 proto tcp from any to any port = 25 pass in quick on eth0 proto tcp from any to any port = 465 pass in quick on eth0 proto tcp from any to any port = 587 pass in quick on eth0 proto tcp from any to any port = 143 pass in quick on eth0 proto tcp from any to any port = 4190
/etc/ipf/ipf6.conf:
pass out from any to any keep state pass in quick proto tcp from any to any port=80 pass in quick proto tcp from any to any port=443 pass in quick proto tcp from any to any port=25 pass in quick proto tcp from any to any port=465 pass in quick proto tcp from any to any port=587 pass in quick proto tcp from any to any port=143 pass in quick proto tcp from any to any port=4190 pass in quick proto icmp from any to any pass out quick proto icmp from any to any pass in quick proto ipv6-icmp from any to any pass out quick proto ipv6-icmp from any to any block in from any to any
Migrating mailboxes
If possible, use dsync.
To migrate mailboxes to poste.io, you can do it one at a time (I had email folders in a directory structure and the standard dovecot mechanism didn't like that in combination with poste.io's maildir structure, so I did it one at a time, moving things into the root folder.
doveadm -o imapc_user=username -o imapc_password=passwd-o imapc_host=hostname -o imapc_ssl=imaps -o imapc_port=993 -o imapc_ssl_verify=no -o ssl_client_ca_dir=/etc/ssl -v backup -R -D -u william@welliver.org -m n imapc:
Upgrading
Backup the following items:
/data
/etc/ipf
/etc/cont-init.d/06-ipf.sh
/etc/protocols
/usr/sbin/p0f
Actually, most of these can be stored in /data/_override and will be automatically put back in on restarts.
Sending mail from postfix servers to poste.io
set up an account that can be used as an authenticated user (but all mail is redirected somewhere else).
pkgsrc cyrus-sasl doesn't include any plugins. The plugins are available, but you'll never find them because they're not named cyrus-sasl-whatever. pkgin av |grep cy2 will list them and then you can install what you need... probably cy2-plugin-login and -plain... Dumb, dumb, dumb.
install mozilla-rootcerts-openssl
postfix changes:
default_database_type = hash
relayhost = [smtp.yourdomain]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/opt/local/etc/postfix/relay_passwd
smtp_sasl_security_options = noanonymous, noplaintext
smtp_sasl_tls_security_options = noanonymous
smtp_sasl_type = cyrus
smtp_tls_CApath = /opt/local/etc/openssl/certs
#optional debugging
smtp_tls_loglevel = 2
smtp_tls_security_level = encrypt
smtp_use_tls = yes
create /opt/local/etc/postfix/relay_passwd:
[smtp.yourdomain]:587 user:password
postmap /opt/local/etc/postfix/relay_passwd
postfix reload